﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
using System.IO;
using Yonkly.Models;
using Yonkly.Utility;
using JsonResult=Yonkly.Models.JsonResult;

namespace Yonkly.Controllers
{
    public class UserController : BaseController
    {
        #region Fields

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="System.Web.Security.MembershipProvider"/> class.
        /// </summary>
        /// <param name="userManager">The membership provider.</param>
        public UserController(MembershipProvider userManager)
            : base(userManager)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UserController"/> class.
        /// </summary>
        public UserController()
        {
        }

        #endregion

        #region Actions

        /// <summary>
        /// Logins the user. This is an Ajax Operation.
        /// </summary>
        /// <param name="userName">Name of the user (Mandatory).</param>
        /// <param name="password">The password (Mandatory).</param>
        /// <param name="rememberMe">If <c>true</c> a persistent cookie is generated.</param>
        public ActionResult Login(string userName, string password, bool rememberMe)
        {
            //using (new CodeBenchmark())
            {
                Yonkly.Models.JsonResult result = new Yonkly.Models.JsonResult();

                if (string.IsNullOrEmpty(userName))
                {
                    result.errorMessage = "User name cannot be blank.";
                }
                else if (string.IsNullOrEmpty(password))
                {
                    result.errorMessage = "Password cannot be blank.";
                }
                else if (!UserManager.ValidateUser(userName, password))
                {
                    result.errorMessage = "Invalid login credentials.";
                }
                else
                {
                    //todo set cookie timeout
                    FormsAuthentication.SetAuthCookie(userName, rememberMe);

                    result.isSuccessful = true;
                }

                return View("Json", result);
            }
        }

        /// <summary>
        /// Logouts the currently logged in user. This is an Ajax Operation.
        /// </summary>
        public ActionResult Logout()
        {
            if (IsUserAuthenticated)
            {
                FormsAuthentication.SignOut();
            }
            return RedirectToAction("Index", "Home");
        }

        /// <summary>
        /// Sends the newly generated random password. This is an Ajax Operation.
        /// </summary>
        /// <param name="email">The email (Mandatory).</param>
        public ActionResult SendPassword(string email)
        {
            //using (new CodeBenchmark())
            {
                Yonkly.Models.JsonResult result = new Yonkly.Models.JsonResult();

                if (string.IsNullOrEmpty(email))
                {
                    result.errorMessage = "Email cannot be blank.";
                }
                else if (!Globals.IsValidEmail(email))
                {
                    result.errorMessage = "Invalid email address.";
                }
                else
                {
                    string userName = UserManager.GetUserNameByEmail(email);

                    if (string.IsNullOrEmpty(userName))
                    {
                        result.errorMessage = "Did not find any user for specified email.";
                    }
                    else
                    {
                        MembershipUser user = UserManager.GetUser(userName, false);

                        string password = user.ResetPassword();

                        //Only send mail when we are not running the unit test.
                        if (HttpContext != null)
                        {
                            try
                            {
                                SendPasswordMail(user.Email, password);
                                result.isSuccessful = true;
                            }
                            catch (Exception e)
                            {
                                result.errorMessage = e.Message;
                            }
                        }
                        else
                        {
                            result.isSuccessful = true;
                        }
                    }
                }

                return View("Json", result);
            }
        }

        /// <summary>
        /// Creates a New User. This is an Ajax Operation.
        /// </summary>
        /// <param name="username">Name of the user (Mandatory and must be unique).</param>
        /// <param name="password">The password.</param>
        /// <param name="email">The email (Mandatory and must be unique).</param>
        public ActionResult Signup(string username, string password, string email)
        {
            //using (new CodeBenchmark())
            {
                JsonResult result = new JsonResult();

                if (Globals.DisableSignup)
                {
                    result.errorMessage = "User signup is currently disabled.  Try back later";
                }
                else if (string.IsNullOrEmpty(username))
                {
                    result.errorMessage = "User name cannot be blank.";
                }
                else if (string.IsNullOrEmpty(password))
                {
                    result.errorMessage = "Password cannot be blank.";
                }
                else if (password.Length < UserManager.MinRequiredPasswordLength)
                {
                    result.errorMessage = string.Format("Password must be {0} character long.",
                                                        UserManager.MinRequiredPasswordLength);
                }
                else if (string.IsNullOrEmpty(email))
                {
                    result.errorMessage = "Email cannot be blank.";
                }
                else if (!Globals.IsValidEmail(email))
                {
                    result.errorMessage = "Invalid email address.";
                }
                else if (!Globals.IsValidUsername(HttpContext, username))
                {
                    result.errorMessage = "Invalid User name.";
                }
                else
                {
                    try
                    {
                        MembershipCreateStatus status;

                        MembershipUser user = UserManager.CreateUser(username, password, email, null, null, true, null,
                                                                     out status);

                        if (user == null)
                        {
                            throw new MembershipCreateUserException(status);
                        }
                        //The following check is required for TDD 
                        if (HttpContext != null)
                        {
                            FormsAuthentication.SetAuthCookie(username, false);
                            try
                            {
                                //Only send mail when we are not running the unit test.
                                SendSignupMail(username, password, email);
                                result.isSuccessful = true;
                            }
                            catch (Exception e)
                            {
                                result.errorMessage = e.Message;
                            }
                        }
                        else
                        {
                            result.isSuccessful = true;
                        }
                    }
                    catch (MembershipCreateUserException e)
                    {
                        result.errorMessage = e.Message;
                    }
                }

                return View("Json", result);
            }
        }

        public ActionResult Follow(Guid userid)
        {
            //using (new CodeBenchmark())
            {
                JsonResult result = new JsonResult();

                if (IsUserAuthenticated)
                {
                    if (userid == LoggedInUserId)
                    {
                        result.isSuccessful = false;
                        result.errorMessage = "You cannot follow yourself.  Make some friends.";
                    }
                    else
                    {
                        //follow user here
                        try
                        {
                            using (YonklyDataContext dc = new YonklyDataContext())
                            {
                                if ((from f in dc.Friends
                                     where f.FriendId == userid &&
                                           f.UserId == LoggedInUserId
                                     select f).Count() > 0)
                                {
                                    result.isSuccessful = false;
                                    result.errorMessage = "You are already following this user";
                                }
                                else
                                {
                                    Friend friend = new Friend();
                                    friend.CreatedOn = DateTime.Now.ToUniversalTime();
                                    friend.ModifiedOn = DateTime.Now.ToUniversalTime();
                                    friend.FriendId = userid;
                                    friend.UserId = LoggedInUserId;
                                    friend.IsFavorite = false;

                                    dc.Friends.InsertOnSubmit(friend);
                                    dc.SubmitChanges();
                                    result.isSuccessful = true;
                                    result.errorMessage = userid.ToString();
                                    var friendUser = UserManager.GetUser(userid, false);
                                    var friendSettings = ProfileController.GetUserSettings(userid);
                                    if (friendSettings.ReceiveEmailOnFollow)
                                    {
                                        SendFollowEmail(friendUser.Email, friendUser.UserName, LoggedInUserName);
                                    }
                                }
                            }
                        }
                        catch (Exception)
                        {
                            result.isSuccessful = false;
                            result.errorMessage = "Unexpected error.";
                            throw;
                        }
                    }
                }
                else
                {
                    result.errorMessage = "You are not logged in.";
                }
                return View("Json", result);
            }
        }

        public ActionResult UnFollow(Guid userid)
        {
            JsonResult result = new JsonResult();
            if (IsUserAuthenticated)
            {
                if (userid == LoggedInUserId)
                {
                    result.isSuccessful = false;
                    result.errorMessage = "You cannot unfollow yourself.";
                }
                else
                {
                    //unfollow user here
                    try
                    {
                        using (YonklyDataContext dc = new YonklyDataContext())
                        {
                            var query = from f in dc.Friends
                                        where f.FriendId == userid &&
                                              f.UserId == LoggedInUserId
                                        select f;
                            if ((query).Count() == 0)
                            {
                                result.isSuccessful = false;
                                result.errorMessage = "You are not following this user";
                            }
                            else
                            {
                                dc.Friends.DeleteOnSubmit(query.First());
                                dc.SubmitChanges();
                                result.isSuccessful = true;
                                result.errorMessage = userid.ToString();
                            }
                        }
                    }
                    catch (Exception)
                    {
                        result.isSuccessful = false;
                        result.errorMessage = "Unexpected error.";
                        throw;
                    }
                }
            }
            else
            {
                result.errorMessage = "You are not logged in.";
            }
            return View("Json", result);
        }


        public ActionResult Find(string searchTerm)
        {
            if (IsUserAuthenticated)
            {
                //SearchResults results = new SearchResults(){SearchTerm = searchTerm};
                SearchResults results = new SearchResults(GetBaseViewData());
                results.SearchTerm = searchTerm;
                using (var dc = new YonklyDataContext())
                {
                    results.Items = (from users in dc.Users
                                     where users.UserName.ToLower().StartsWith(searchTerm.ToLower()) ||
                                           users.Membership.Email.ToLower() == searchTerm.ToLower()
                                     select
                                         new ResultItem
                                             {
                                                 Username = users.UserName,
                                                 Email = users.Membership.Email,
                                                 UserId = users.UserId
                                             }).Take(5).ToList();
                }

                return View("Find", results);
            }
            else
            {
                return View("Login", GetBaseViewData());
            }
        }

        protected override void HandleUnknownAction(string actionName)
        {
            View(actionName, 0);
        }

        public ActionResult View(string username, int? page)
        {
            if (string.IsNullOrEmpty(username))
            {
                return RedirectToAction("Index", "Home");
            }
            else
            {
                if (username.ToLower() == LoggedInUserName.ToLower())
                {
                    return View("Home", GetMyMessagesViewData(username, page));
                }
                else
                {
                    if (UserManager.GetUser(username, false) == null)
                    {
                        return View("Error", GetBaseViewData());
                    }
                    else
                    {
                        return View("Home", GetMyMessagesViewData(username, page));
                    }
                }
            }
        }

        #endregion

        #region Private methods

        protected MessagesViewData GetMyMessagesViewData(string username, int? page)
        {
            //todo refactor this function
            page = page ?? 0;
            var viewData = new MessagesViewData(GetBaseViewData(username));

            //get user messages
            using (var dc = new YonklyDataContext())
            {
                var mymessages = from messages in dc.ViewMessages
                                 where messages.SenderId == viewData.CurrentUser.UserId ||
                                       messages.RecipientId == viewData.CurrentUser.UserId
                                 select messages;
                var friendmessages = from messages in dc.ViewMessages
                                     join friends in dc.Friends on messages.SenderId equals friends.FriendId
                                     where friends.UserId == viewData.CurrentUser.UserId
                                     select messages;

                var messagestream = mymessages.Union(friendmessages);

                var query = (from messages in messagestream
                             select new MessageItem(viewData, messages) { CreatedOn = messages.CreatedOn }
                            ).OrderByDescending(m => m.CreatedOn);

                viewData.Messages = query.ToPagedList((int)page, Globals.PageSize);
            }
            return viewData;
        }

        private void SendFollowEmail(string email, string friendUserName, string followerUserName)
        {
            try
            {
                const string CacheKey = "followMailTemplate";

                string body = HttpContext.Cache[CacheKey] as string;

                if (body == null)
                {
                    string file = System.Web.HttpContext.Current.Server.MapPath("~/content/MailTemplates/follow.txt");
                    body = File.ReadAllText(file);

                    if (HttpContext.Cache[CacheKey] == null)
                    {
                        HttpContext.Cache[CacheKey] = body;
                    }
                }

                body = body.Replace("<%friend%>", friendUserName);
                body = body.Replace("<%follower%>", followerUserName);
                body = body.Replace("<%followerUrl%>", string.Format("{0}/{1}", Globals.SiteUrl, followerUserName));

                SendMail(email, "Yonkly.com: New follower", body);
            }
            catch
            {
                //ignore email errors
                //todo need to log these errors
            }
        }

        private void SendPasswordMail(string email, string password)
        {
            const string CacheKey = "passwordRecoveryMailTemplate";

            string body = HttpContext.Cache[CacheKey] as string;

            if (body == null)
            {
                string file = System.Web.HttpContext.Current.Server.MapPath("~/content/MailTemplates/Password.txt");
                body = File.ReadAllText(file);

                if (HttpContext.Cache[CacheKey] == null)
                {
                    HttpContext.Cache[CacheKey] = body;
                }
            }

            body = body.Replace("<%Password%>", password);

            SendMail(email, "Yonkly.com: New Password", body);
        }

        private void SendSignupMail(string userName, string password, string email)
        {
            const string CacheKey = "signupMailTemplate";

            string body = HttpContext.Cache[CacheKey] as string;

            if (body == null)
            {
                string file = System.Web.HttpContext.Current.Server.MapPath("~/content/MailTemplates/Signup.txt");
                body = File.ReadAllText(file);

                if (HttpContext.Cache[CacheKey] == null)
                {
                    HttpContext.Cache[CacheKey] = body;
                }
            }

            body = body.Replace("<%UserName%>", userName);
            body = body.Replace("<%Password%>", password);

            SendMail(email, "Yonkly.com: Signup", body);
        }

        [NonAction]
        public static List<UserItem> GetSidebarFriends(Guid currentUserId, Guid loggedInUserId)
        {
            using (YonklyDataContext dc = new YonklyDataContext())
            {
                var takeCount = 30;
                var query = (from f in dc.Friends
                             where f.UserId == currentUserId
                             select new UserItem
                                        {
                                            Email = f.FriendUser.Membership.Email,
                                            UserName = f.FriendUser.UserName,
                                            UserId = f.FriendId
                                        }).Take(takeCount);
                return query.ToList();
            }
        }

        [NonAction]
        public static List<UserItem> GetSidebarFriendsOfFriends(Guid loggedInUserId)
        {
            var takeCount = 30;
            if (loggedInUserId == Guid.Empty)
                return new List<UserItem>();
            using (var dc = new YonklyDataContext())
            {
                var myf = (from f in dc.Friends
                           where f.UserId == loggedInUserId
                           select new
                                      {
                                          Email = f.FriendUser.Membership.Email,
                                          UserName = f.FriendUser.UserName,
                                          UserId = f.FriendId
                                      });

                var myfof = (from fof in dc.Friends
                             from f in dc.Friends
                             where fof.UserId == f.FriendId
                                   && f.UserId == loggedInUserId
                                   && fof.FriendId != loggedInUserId
                             select new
                                        {
                                            Email = fof.FriendUser.Membership.Email,
                                            UserName = fof.FriendUser.UserName,
                                            UserId = fof.FriendId
                                        }
                            );
                var query = myfof.Except(myf)
                    .Select(f => new UserItem
                                     {
                                         Email = f.Email,
                                         UserName = f.UserName,
                                         UserId = f.UserId
                                     }).Take(takeCount);


                return query.ToList();
            }
        }

        [NonAction]
        private static List<UserItem> GetSidebarNonMutualFriends(Guid currentUserId, Guid loggedInUserId)
        {
            var takeCount = 30;
            if (currentUserId == loggedInUserId)
                return new List<UserItem>();
            using (YonklyDataContext dc = new YonklyDataContext())
            {
                var query = dc.Friends.Where(f => f.UserId == currentUserId && f.FriendId != loggedInUserId)
                    .Select(f => new
                                     {
                                         Email = f.FriendUser.Membership.Email,
                                         UserName = f.FriendUser.UserName,
                                         UserId = f.FriendId
                                     })
                    .Except(dc.Friends.Where(f => f.UserId == loggedInUserId)
                                .Select(f => new
                                                 {
                                                     Email = f.FriendUser.Membership.Email,
                                                     UserName = f.FriendUser.UserName,
                                                     UserId = f.FriendId
                                                 }))
                    .Select(f => new UserItem
                                     {
                                         Email = f.Email,
                                         UserName = f.UserName,
                                         UserId = f.UserId
                                     }).Take(takeCount);

                return query.ToList();
            }
        }

        #endregion
    }
}
